﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using nRoute.Components;
using nRoute.Components.Composition;
using Orktane.Components;
using Orktane.ScriptActionModel;

namespace Orktane.Behaviors
{
    public class ScriptActionConsoleExtenstion 
    {

#region Declarations

        private readonly static char[] START_TRIMCHARS = new char[] { ' ', Environment.NewLine.ToCharArray()[0] };
        private const string UNKOWN_COMMAND_MSG = "'{0}' is not recognized as an internal or external command.";
        private const string SPACE = " ";
        private const string ERROR_FORMAT = "ERROR: /n/n{0}";
        private const ConsoleColor DEFAULT_BACKGROUND = ConsoleColor.DarkBlue;
        private const string DEFAULT_TITLE = "Console Window";
        private const string DEFAULT_PREFIX = "SL> ";
        private const string PREVIOUS_ENTRY_FORMAT = "SL> {0}";

        private readonly static Dictionary<string, Action<IConsole, string>> _internalCommands;

        private readonly IConsole _console;
        private IConsoleScriptAction _currentConsoleScriptAction;

#endregion

        static ScriptActionConsoleExtenstion()
        {
            _internalCommands = new Dictionary<string, Action<IConsole, string>>(StringComparer.InvariantCultureIgnoreCase);
            InitializeInternalCommands();
        }

        public ScriptActionConsoleExtenstion(IConsole console)
        {
            if (console == null) throw new ArgumentNullException("console");
            _console = console;
            _console.CancelKeyPress += new EventHandler(Console_CancelKeyPress);

            // and initialize
            InitializeConsole();
        }

#region Properties

        public IConsole Console
        {
            get { return _console; }
        }

#endregion

#region Console Related

        private void InitializeConsole()
        {
            _console.Title = DEFAULT_TITLE;
            _console.Prompt = DEFAULT_PREFIX;
            _console.BackgroundColor = DEFAULT_BACKGROUND;
            _console.ReadLine(OnCommand);
        }

        private void OnCommand(string command)
        {
            // basic check
            if (string.IsNullOrEmpty(command)) return;

            // we get the command and its args
            string _arguments;
            var _command = ParseCommandAndArguments(command, out _arguments);
            if (string.IsNullOrEmpty(_command)) return;

            // write the command
            Console.WriteLine(PREVIOUS_ENTRY_FORMAT, command);
            _currentConsoleScriptAction = null;

            // we get the 
            if (TryExecuteAsInternalCommand(_command, _arguments))
            {
                return;
            }
            else if (ScriptActionsManager.CanExecuteScript(_command))
            {
                // we remove our prefix
                _console.Prompt = string.Empty;

                // we get the script locator, info and args
                ScriptActionInfo _scriptActionInfo;
                Arguments _scriptArguments;

                // get the locator
                IResourceLocator _scriptLocator = ScriptActionsManager.ResolveScriptLocator(command, out _scriptActionInfo, out _scriptArguments);

                // and then we execute
                if (_scriptActionInfo.IsConsoleScript)
                {
                    ExecuteAsExternalConsoleCommand(_scriptLocator, _scriptArguments);
                }
                else
                {
                    ExecuteAsExternalCommand(_scriptLocator, _scriptArguments);
                }
            }
            else
            {
                _console.WriteLine(UNKOWN_COMMAND_MSG, _command);
            }
            Console.WriteLine();
        }

        private bool TryExecuteAsInternalCommand(string command, string arguments)
        {
            // and we get the command return true
            if (!_internalCommands.ContainsKey(command)) return false;
            _internalCommands[command](_console, arguments);
            if (!_console.DisableInput) _console.WriteLine();
            return true;
        }

        private void ExecuteAsExternalCommand(IResourceLocator scriptLocator, Arguments scriptArguments)
        {
            try
            {
                // get the script
                var _scriptAction = (IScriptAction)scriptLocator.GetResourceInstance();
                _scriptAction.Execute(scriptArguments);
                ResetConsole();
            }
            catch (Exception ex)
            {
                _console.WriteLine(ERROR_FORMAT, ex.ToString());
                _console.WriteLine();
                ResetConsole();
                Debugger.Break();
            }
        }

        private void ExecuteAsExternalConsoleCommand(IResourceLocator scriptLocator, Arguments scriptArguments)
        {
            // get the script
            _currentConsoleScriptAction = (IConsoleScriptAction)scriptLocator.GetResourceInstance();

            try
            {
                // and we execute
                var _dispoable = new RelayDisposable(() => CloseConsoleScriptAction(_currentConsoleScriptAction));
                _currentConsoleScriptAction.Intialize(_console, _dispoable);
                _currentConsoleScriptAction.Execute(scriptArguments);
            }
            catch (Exception ex)
            {
                _console.WriteLine(ERROR_FORMAT, ex.ToString());
                _console.WriteLine();
                CloseConsoleScriptAction(_currentConsoleScriptAction);
                Debugger.Break();
            }
        }

        private void CloseConsoleScriptAction(IConsoleScriptAction scriptAction)
        {
            // we close the script action
            if (scriptAction != null)
            {
                try
                {
                    scriptAction.Close();
                }
                catch { }
            }
            _currentConsoleScriptAction = null;

            // and reset
            ResetConsole();
        }

        private void ResetConsole()
        {
            // ensure this is enabled
            _console.DisableInput = false;
            _console.DisabledInputText = string.Empty;
            _console.ClearInput();

            // and resume our script handling
            _console.Prompt = DEFAULT_PREFIX;
            _console.ClearInput();
            _console.ReadLine(OnCommand);
        }
        
#endregion

#region Handlers

        private void Console_CancelKeyPress(object sender, EventArgs e)
        {
            if (_currentConsoleScriptAction != null)
            {
                try
                {
                    // if we can cancel then
                    if (_currentConsoleScriptAction.Cancel())
                    {
                        CloseConsoleScriptAction(_currentConsoleScriptAction);
                    }
                }
                catch 
                {
                    CloseConsoleScriptAction(_currentConsoleScriptAction);
                }
            }
        }

#endregion

#region Helpers

        private static void InitializeInternalCommands()
        {
            _internalCommands.Add("cls", (c, s) => c.ClearOutput());
            _internalCommands.Add("echo", (c, s) => c.WriteLine(s ?? string.Empty));
            _internalCommands.Add("beep", (c, s) => c.Beep());
        }

        private static string ParseCommandAndArguments(string script, out string scriptArguments)
        {
            // by default
            scriptArguments = null;

            // basic checks
            if (string.IsNullOrEmpty(script)) return null;
            if (script.Trim().Length == 0) return null;

            // we get the prefix
            string _trimmedScript = script.TrimStart(START_TRIMCHARS);
            var _firstSpaceIndex = _trimmedScript.IndexOf(SPACE);

            // if there is no first space index, then whole word should be a prefix
            string _commandName = (_firstSpaceIndex == -1) ? _trimmedScript.Trim() :
                _trimmedScript.Substring(0, _firstSpaceIndex).Trim();

            // and return
            if (_firstSpaceIndex != -1) scriptArguments = _trimmedScript.Substring(_firstSpaceIndex + 1);
            return _commandName;
        }

#endregion

    }
}